package org.hashdb.client.console.completer;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import jline.console.completer.Completer;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.hashdb.client.console.AppConsoleReader;
import org.hashdb.client.net.bio.MessageChannel;
import org.hashdb.client.net.bio.get.ActCommandMessage;
import org.hashdb.client.net.bio.send.CommandMessage;
import org.hashdb.client.util.JsonService;
import org.jetbrains.annotations.Nullable;

import java.nio.channels.ClosedChannelException;
import java.util.List;
import java.util.Map;

/**
 * Date: 2024/1/10 1:11
 *
 * @author huanyuMake-pecdle
 * @version 0.0.1
 */
@RequiredArgsConstructor
public class DatabaseNameCompleter implements Completer, RemoteCompleter {
    private final MessageChannel channel;

    private final AppConsoleReader consoleReader;

    @Override
    @SneakyThrows
    public int complete(String buffer, int cursor, List<CharSequence> candidates) {
        if (buffer.matches("dbuse\\s*")) {
            return completeNoDBName(cursor, candidates);
        }
        if (buffer.matches("dbuse .*")) {
            String prefix = buffer.substring(6);
            return completeWithPrefix(prefix, cursor, candidates);
        }
        return -1;
    }

    private int completeWithPrefix(String prefix, int cursor, List<CharSequence> candidates) throws JsonProcessingException, ClosedChannelException {
        var body = getDBInfos();
        if (body == null) {
            return candidates.isEmpty() ? -1 : 0;
        }
        for (Map<String, Object> dbInfo : body) {
            var nameObj = dbInfo.get("name");
            if (!(nameObj instanceof String name)) {
                throw new RuntimeException("unexpect condition 'name of dbInfo is required'");
            }
            if (!name.startsWith(prefix)) {
                continue;
            }
            candidates.add(name);
        }
        return candidates.isEmpty() ? -1 : cursor - prefix.length();
    }

    private int completeNoDBName(int cursor, List<CharSequence> candidates) {
        try {
            var body = getDBInfos();
            if (body == null) {
                return candidates.isEmpty() ? -1 : 0;
            }
            for (Map<String, Object> dbInfo : body) {
                var nameObj = dbInfo.get("name");
                if (!(nameObj instanceof String name)) {
                    throw new RuntimeException("unexpect condition 'name of dbInfo is required'");
                }
                candidates.add(name);
            }
        } catch (ClosedChannelException | JsonProcessingException e) {
            consoleReader.printStackTrace(e);
        }
        return candidates.isEmpty() ? -1 : cursor;
    }

    @Nullable
    private List<Map<String, Object>> getDBInfos() throws ClosedChannelException, JsonProcessingException {
        if (!channel.isConnected()) {
            consoleReader.removeCompleter(this);
            return null;
        }
        Object dbshowResp = channel.send(new CommandMessage("dbshow")).join();
        if (!(dbshowResp instanceof ActCommandMessage result)) {
            consoleReader.errorLn("unexpected result: '" + dbshowResp + "'");
            return null;
        }
        return JsonService.parse(result.getData(), new TypeReference<List<Map<String, Object>>>() {
        });
    }
}
